// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

use crate::args::get_root_cert_store;
use crate::args::npm_pkg_req_ref_to_binary_command;
use crate::args::CaData;
use crate::args::CacheSetting;
use crate::args::PackageJsonDepsProvider;
use crate::args::StorageKeyResolver;
use crate::cache::Caches;
use crate::cache::DenoDirProvider;
use crate::cache::NodeAnalysisCache;
use crate::file_fetcher::get_source_from_data_url;
use crate::http_util::HttpClient;
use crate::module_loader::CjsResolutionStore;
use crate::module_loader::CliNodeResolver;
use crate::module_loader::NpmModuleLoader;
use crate::node::CliCjsCodeAnalyzer;
use crate::npm::create_cli_npm_resolver;
use crate::npm::CliNpmResolverCreateOptions;
use crate::npm::CliNpmResolverManagedCreateOptions;
use crate::npm::CliNpmResolverManagedPackageJsonInstallerOption;
use crate::npm::CliNpmResolverManagedSnapshotOption;
use crate::npm::NpmCacheDir;
use crate::resolver::MappedSpecifierResolver;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
use crate::util::v8::construct_v8_flags;
use crate::worker::CliMainWorkerFactory;
use crate::worker::CliMainWorkerOptions;
use crate::worker::ModuleLoaderFactory;
use deno_ast::MediaType;
use deno_core::anyhow::Context;
use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::v8_set_flags;
use deno_core::FeatureChecker;
use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::ModuleType;
use deno_core::ResolutionKind;
use deno_runtime::deno_fs;
use deno_runtime::deno_node::analyze::NodeCodeTranslator;
use deno_runtime::deno_node::NodeResolver;
use deno_runtime::deno_tls::rustls::RootCertStore;
use deno_runtime::deno_tls::RootCertStoreProvider;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
use deno_runtime::WorkerLogLevel;
use deno_semver::npm::NpmPackageReqReference;
use import_map::parse_from_json;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;

mod binary;
mod file_system;
mod virtual_fs;

pub use binary::extract_standalone;
pub use binary::is_standalone_binary;
pub use binary::DenoCompileBinaryWriter;

use self::binary::load_npm_vfs;
use self::binary::Metadata;
use self::file_system::DenoCompileFileSystem;

struct SharedModuleLoaderState {
    eszip: eszip::EszipV2,
    mapped_specifier_resolver: MappedSpecifierResolver,
    node_resolver: Arc<CliNodeResolver>,
    npm_module_loader: Arc<NpmModuleLoader>,
}

#[derive(Clone)]
struct EmbeddedModuleLoader {
    shared: Arc<SharedModuleLoaderState>,
    root_permissions: PermissionsContainer,
    dynamic_permissions: PermissionsContainer,
}

impl ModuleLoader for EmbeddedModuleLoader {
    fn resolve(&self, specifier: &str, referrer: &str, kind: ResolutionKind) -> Result<ModuleSpecifier, AnyError> {
        let referrer = if referrer == "." {
            if kind != ResolutionKind::MainModule {
                return Err(generic_error(format!("Expected to resolve main module, got {:?} instead.", kind)));
            }
            let current_dir = std::env::current_dir().unwrap();
            deno_core::resolve_path(".", &current_dir)?
        } else {
            ModuleSpecifier::parse(referrer).map_err(|err| type_error(format!("Referrer uses invalid specifier: {}", err)))?
        };

        let permissions = if matches!(kind, ResolutionKind::DynamicImport) { &self.dynamic_permissions } else { &self.root_permissions };
        if let Some(result) = self.shared.node_resolver.resolve_if_in_npm_package(specifier, &referrer, permissions) {
            return result;
        }

        let maybe_mapped = self.shared.mapped_specifier_resolver.resolve(specifier, &referrer)?.into_specifier();

        // npm specifier
        let specifier_text = maybe_mapped.as_ref().map(|r| r.as_str()).unwrap_or(specifier);
        if let Ok(reference) = NpmPackageReqReference::from_str(specifier_text) {
            return self.shared.node_resolver.resolve_req_reference(&reference, permissions, &referrer);
        }

        match maybe_mapped {
            Some(resolved) => Ok(resolved),
            None => deno_core::resolve_import(specifier, referrer.as_str()).map_err(|err| err.into()),
        }
    }

    fn load(&self, original_specifier: &ModuleSpecifier, maybe_referrer: Option<&ModuleSpecifier>, is_dynamic: bool) -> Pin<Box<deno_core::ModuleSourceFuture>> {
        let is_data_uri = get_source_from_data_url(original_specifier).ok();
        if let Some((source, _)) = is_data_uri {
            return Box::pin(deno_core::futures::future::ready(Ok(deno_core::ModuleSource::new(deno_core::ModuleType::JavaScript, source.into(), original_specifier))));
        }

        let permissions = if is_dynamic { &self.dynamic_permissions } else { &self.root_permissions };
        if let Some(result) = self.shared.npm_module_loader.load_sync_if_in_npm_package(original_specifier, maybe_referrer, permissions) {
            return match result {
                Ok(code_source) => Box::pin(deno_core::futures::future::ready(Ok(deno_core::ModuleSource::new_with_redirect(
                    match code_source.media_type {
                        MediaType::Json => ModuleType::Json,
                        _ => ModuleType::JavaScript,
                    },
                    code_source.code,
                    original_specifier,
                    &code_source.found_url,
                )))),
                Err(err) => Box::pin(deno_core::futures::future::ready(Err(err))),
            };
        }

        let Some(module) = self.shared.eszip.get_module(original_specifier.as_str()) else {
            return Box::pin(deno_core::futures::future::ready(Err(type_error(format!("Module not found: {}", original_specifier)))));
        };
        let original_specifier = original_specifier.clone();
        let found_specifier = ModuleSpecifier::parse(&module.specifier).expect("invalid url in eszip");

        async move {
            let code = module.source().await.ok_or_else(|| type_error(format!("Module not found: {}", original_specifier)))?;
            let code = arc_u8_to_arc_str(code).map_err(|_| type_error("Module source is not utf-8"))?;
            Ok(deno_core::ModuleSource::new_with_redirect(
                match module.kind {
                    eszip::ModuleKind::JavaScript => ModuleType::JavaScript,
                    eszip::ModuleKind::Json => ModuleType::Json,
                    eszip::ModuleKind::Jsonc => return Err(type_error("jsonc modules not supported")),
                    eszip::ModuleKind::OpaqueData => {
                        unreachable!();
                    }
                },
                code.into(),
                &original_specifier,
                &found_specifier,
            ))
        }
            .boxed_local()
    }
}

fn arc_u8_to_arc_str(arc_u8: Arc<[u8]>) -> Result<Arc<str>, std::str::Utf8Error> {
    // Check that the string is valid UTF-8.
    std::str::from_utf8(&arc_u8)?;
    // SAFETY: the string is valid UTF-8, and the layout Arc<[u8]> is the same as
    // Arc<str>. This is proven by the From<Arc<str>> impl for Arc<[u8]> from the
    // standard library.
    Ok(unsafe { std::mem::transmute(arc_u8) })
}

struct StandaloneModuleLoaderFactory {
    shared: Arc<SharedModuleLoaderState>,
}

impl ModuleLoaderFactory for StandaloneModuleLoaderFactory {
    fn create_for_main(&self, root_permissions: PermissionsContainer, dynamic_permissions: PermissionsContainer) -> Rc<dyn ModuleLoader> {
        Rc::new(EmbeddedModuleLoader {
            shared: self.shared.clone(),
            root_permissions,
            dynamic_permissions,
        })
    }

    fn create_for_worker(&self, root_permissions: PermissionsContainer, dynamic_permissions: PermissionsContainer) -> Rc<dyn ModuleLoader> {
        Rc::new(EmbeddedModuleLoader {
            shared: self.shared.clone(),
            root_permissions,
            dynamic_permissions,
        })
    }

    fn create_source_map_getter(&self) -> Option<Box<dyn deno_core::SourceMapGetter>> {
        None
    }
}

struct StandaloneRootCertStoreProvider {
    ca_stores: Option<Vec<String>>,
    ca_data: Option<CaData>,
    cell: once_cell::sync::OnceCell<RootCertStore>,
}

impl RootCertStoreProvider for StandaloneRootCertStoreProvider {
    fn get_or_try_init(&self) -> Result<&RootCertStore, AnyError> {
        self.cell.get_or_try_init(|| get_root_cert_store(None, self.ca_stores.clone(), self.ca_data.clone()).map_err(|err| err.into()))
    }
}

